-
Notifications
You must be signed in to change notification settings - Fork 2.7k
fix: Always put the well-known endpoints at the server root #1288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Always put the well-known endpoints at the server root #1288
Conversation
# Bug Report: Incorrect `.well-known/oauth-protected-resource` endpoint path when `resource_server_url` ends with `/sse`
## Summary
When configuring FastMCP with OAuth2 authentication and setting the `resource_server_url` to end with `/sse` (as required by VSCode MCP clients), FastMCP incorrectly serves the `.well-known/oauth-protected-resource` endpoint at `/sse/.well-known/oauth-protected-resource` instead of the expected root path `/.well-known/oauth-protected-resource`.
## Environment
- **FastMCP version**: Part of `mcp` Python library (check with `pip show mcp`)
- **Python version**: 3.13
- **Operating System**: macOS
- **MCP Client**: VSCode with MCP extension
- **Affected Files**:
- `mcp/server/fastmcp/server.py` (lines ~790-797)
- `mcp/server/auth/routes.py` (lines ~215-224)
## Expected Behavior
1. The `.well-known/oauth-protected-resource` endpoint should always be served at the root path (`/.well-known/oauth-protected-resource`) regardless of the `resource_server_url` configuration
2. The `resource` field in the `.well-known` response should point to the actual protected resource (e.g., `/sse`)
3. OAuth2 discovery should work correctly with MCP clients like VSCode
## Actual Behavior
When `resource_server_url` is set to `http://localhost:8099/sse`, FastMCP:
1. Serves `.well-known/oauth-protected-resource` at `/sse/.well-known/oauth-protected-resource`
2. The SSE endpoint `/sse` returns a `www-authenticate` header with `resource_metadata="http://localhost:8099/sse/.well-known/oauth-protected-resource"`
3. OAuth2 discovery fails because clients expect the `.well-known` endpoint at the root
## Steps to Reproduce
1. Create a FastMCP server with OAuth2 configuration:
```python
from mcp.server.fastmcp import FastMCP
from mcp.server.auth.settings import AuthSettings
from pydantic import AnyHttpUrl
# Configure auth settings with /sse endpoint
auth_settings = AuthSettings(
issuer_url=AnyHttpUrl("https://login.microsoftonline.com/tenant-id/v2.0"),
resource_server_url=AnyHttpUrl("http://localhost:8099/sse"), # Note: ends with /sse
required_scopes=["https://example.com/scope"]
)
mcp = FastMCP(
"Test Server",
token_verifier=your_token_verifier,
auth=auth_settings,
)
app = mcp.sse_app()
```
2. Start the server: `uvicorn server:app --port 8099`
3. Test the endpoints:
```bash
# This should work but returns 404
curl http://localhost:8099/.well-known/oauth-protected-resource
# This works but shouldn't be the location
curl http://localhost:8099/sse/.well-known/oauth-protected-resource
# SSE endpoint references wrong .well-known location
curl -I http://localhost:8099/sse
# Returns: resource_metadata="http://localhost:8099/sse/.well-known/oauth-protected-resource"
```
## Root Cause Analysis
**Exact Location of Bug**:
- **File**: `mcp/server/fastmcp/server.py`
- **Lines**: ~790-797 in the `sse_app()` method
- **Function**: `FastMCP.sse_app()`
**The Issue**: When setting up OAuth2 authentication, FastMCP constructs the `resource_metadata_url` incorrectly:
```python
# BUGGY CODE - Line ~790-797 in sse_app() method
resource_metadata_url = AnyHttpUrl(
str(self.settings.auth.resource_server_url).rstrip("/") + "/.well-known/oauth-protected-resource"
)
```
When `resource_server_url` is `http://localhost:8099/sse`, this creates `http://localhost:8099/sse/.well-known/oauth-protected-resource`.
However, the actual `.well-known` endpoint is created by `create_protected_resource_routes()` (in `mcp/server/auth/routes.py` lines ~215-224), which always creates it at the root path:
```python
# CORRECT CODE - This always creates /.well-known/oauth-protected-resource at root
return [
Route(
"/.well-known/oauth-protected-resource",
endpoint=cors_middleware(handler.handle, ["GET", "OPTIONS"]),
methods=["GET", "OPTIONS"],
)
]
```
**The Fix**: The `resource_metadata_url` should be constructed from the base URL, not the `resource_server_url`:
```python
# PROPOSED FIX
if self.settings.auth and self.settings.auth.resource_server_url:
from pydantic import AnyHttpUrl
from urllib.parse import urlparse
# Extract base URL from resource_server_url
parsed = urlparse(str(self.settings.auth.resource_server_url))
base_url = f"{parsed.scheme}://{parsed.netloc}"
resource_metadata_url = AnyHttpUrl(
base_url + "/.well-known/oauth-protected-resource"
)
```
## Impact
- **High**: Breaks OAuth2 discovery for MCP clients like VSCode
- MCP servers cannot be properly authenticated when using the recommended `/sse` resource URL pattern
- Workarounds require custom endpoint overrides, defeating the purpose of built-in auth support
## Proposed Solution
The `.well-known/oauth-protected-resource` endpoint should always be served at the root path (`/.well-known/oauth-protected-resource`), regardless of the `resource_server_url` configuration. The `resource_server_url` should only affect:
1. The `resource` field value in the `.well-known` response
2. The `resource_metadata` reference in `www-authenticate` headers
## Current Workaround
Override the built-in `.well-known` endpoint with a custom implementation:
```python
async def custom_well_known_endpoint(request):
return JSONResponse({
"resource": f"{config.EXTERNAL_ADDRESS}/sse",
"authorization_servers": ["https://login.microsoftonline.com/tenant/v2.0"],
"scopes_supported": ["https://example.com/scope"],
"bearer_methods_supported": ["header"]
})
# Override the built-in endpoint
app.router.routes.insert(0, Route("/.well-known/oauth-protected-resource", custom_well_known_endpoint, methods=["GET"]))
```
## Additional Context
- This issue specifically affects integration with VSCode MCP clients, which require the resource URL to end with `/sse`
- The OAuth2 specification (RFC 8414) defines `.well-known` endpoints should be at predictable root paths
- Other OAuth2 implementations (e.g., Auth0, Okta) serve `.well-known` endpoints at root regardless of resource configuration
## Related Documentation
- [RFC 8414 - OAuth 2.0 Authorization Server Metadata](https://tools.ietf.org/html/rfc8414)
- [MCP OAuth2 Authentication Documentation](https://modelcontextprotocol.io/docs/concepts/authentication)
|
Can we speed up reviewing current PR. I ran into the same issue with vscode and solved by using your branch. Besides, the sample code will also need to be modified. (
|
|
Question... The listed Expected Behavior in this pull request states: "The .well-known/oauth-protected-resource endpoint should always be served at the root path (/.well-known/oauth-protected-resource) regardless of the resource_server_url configuration". That seems the exact opposite of what would be needed in use cases where the MCP server must run at a custom path. When attached to a GKE gateway + HttpRoute (or api management, such as Apigee or APIM), serving the well-known endpoints from the root path is always going to return 404 responses. |
|
Where did you get Actually I have opposite problem: |
|
I agree that if someone is hosting an mcp endpoint somewhere deep into a path structure that this PR will not work. I suggest being able to explicitly configure the protected resource path. Currently this library is not compatible with VSCode's protected resource resolution. You must supply an "/sse" endpoint in the mcp configuration and then this library will host the well known endpoint at "/sse/.well-known" which is not what VSCode requests. If you feel this is a bug in VSCode then we can close the PR and go on our way and people cause use the workaround I provided. Is there anything in the standard that clarifies these paths for auth? Is VSCode supposed to require you to include the "/sse" portion of the URL? |
|
This change moves things closer to the RFC, so generally supportive. If you could add a test, then we can merge this. There's a draft PR to the spec to clarify the preference order here that should get merged today: With ^ the way to handle a deeply nested metadata document is to indicate it in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(see above, please add a test)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requesting changes to implement a test + there seem to be some CI failures still.
|
Hi @pcarleton, @felixweinberger - After reviewing the PR and the linked issue, I don’t think the proposed fix fully resolves the problem. Root cause: But, the server route is registered at following URL all the tme: These don’t align. Besides, the RFC 9728 spec expects the I have created a pull request to implement such fixes. Please see: |
Bug Report: Incorrect
.well-known/oauth-protected-resourceendpoint path whenresource_server_urlends with/sseThis is reported in 1264
Summary
When configuring FastMCP with OAuth2 authentication and setting the
resource_server_urlto end with/sse(as required by VSCode MCP clients), FastMCP incorrectly serves the.well-known/oauth-protected-resourceendpoint at/sse/.well-known/oauth-protected-resourceinstead of the expected root path/.well-known/oauth-protected-resource.Environment
mcpPython library (check withpip show mcp)mcp/server/fastmcp/server.py(lines ~790-797)mcp/server/auth/routes.py(lines ~215-224)Expected Behavior
.well-known/oauth-protected-resourceendpoint should always be served at the root path (/.well-known/oauth-protected-resource) regardless of theresource_server_urlconfigurationresourcefield in the.well-knownresponse should point to the actual protected resource (e.g.,/sse)Actual Behavior
When
resource_server_urlis set tohttp://localhost:8099/sse, FastMCP:.well-known/oauth-protected-resourceat/sse/.well-known/oauth-protected-resource/ssereturns awww-authenticateheader withresource_metadata="http://localhost:8099/sse/.well-known/oauth-protected-resource".well-knownendpoint at the rootSteps to Reproduce
Start the server:
uvicorn server:app --port 8099Test the endpoints:
Root Cause Analysis
Exact Location of Bug:
mcp/server/fastmcp/server.pysse_app()methodFastMCP.sse_app()The Issue: When setting up OAuth2 authentication, FastMCP constructs the
resource_metadata_urlincorrectly:When
resource_server_urlishttp://localhost:8099/sse, this createshttp://localhost:8099/sse/.well-known/oauth-protected-resource.However, the actual
.well-knownendpoint is created bycreate_protected_resource_routes()(inmcp/server/auth/routes.pylines ~215-224), which always creates it at the root path:The Fix: The
resource_metadata_urlshould be constructed from the base URL, not theresource_server_url:Impact
/sseresource URL patternProposed Solution
The
.well-known/oauth-protected-resourceendpoint should always be served at the root path (/.well-known/oauth-protected-resource), regardless of theresource_server_urlconfiguration. Theresource_server_urlshould only affect:resourcefield value in the.well-knownresponseresource_metadatareference inwww-authenticateheadersCurrent Workaround
Override the built-in
.well-knownendpoint with a custom implementation:Additional Context
/sse.well-knownendpoints should be at predictable root paths.well-knownendpoints at root regardless of resource configurationRelated Documentation